#include <iostream>
#include "syntaxCheck.h"

using namespace std;

void SyntaxCheck::run() throw()
{
    cout << "Analyzing syntax..." << endl;
    count = 0;
    program();
    cout <<"Syntax is correct." << endl;
}

void SyntaxCheck::program() throw()
{
    //keyword: 'program'
    currentToken = &tokens.at(count); //count == 0
    if (currentToken->getText() != "program") syntaxError("Keyword 'program' was not found!");

    //enter into global scope
    symbolsTable.enterScope();
    //identifier: name of the program
    getNextToken();
    if (currentToken->getClassification() != Token::Classifications::IDENTIFIER) syntaxError("Invalid identifier for program!");

    string pr = "program";
    symbolsTable.addSymbol(currentToken->getText(), pr);
    //identifier: ';'
    getNextToken();
    if (currentToken->getText() != ";") syntaxError("Symbol ';' was not found!");

    //control flow
    varDeclaration();
    subProgramsDeclaration();
    compoundCommand();

    //exit scope
    symbolsTable.exitScope();

    //delimiter: '.'
    getNextToken();
    if (currentToken->getText() != ".") syntaxError("Symbol '.' was not found!");
}

void SyntaxCheck::varDeclaration() throw()
{
    getNextToken();
    if (currentToken->getText() == "var")
    {
        varDeclarationListA();
    }
    else
    {
        count--;
    }
}

void SyntaxCheck::varDeclarationListA() throw()
{
    identifiersListA();

    getNextToken();
    if (currentToken->getText()!= ":") syntaxError("Symbol ':' was not found!");

    type();

    getNextToken();
    if (currentToken->getText() != ";") syntaxError("Symbol ';' was not found!");

    varDeclarationListB();
}

void SyntaxCheck::varDeclarationListB() throw()
{
    getNextToken();
    if (currentToken->getClassification() != (Token::Classifications::IDENTIFIER))
    {
        count--;
        return;
    }
    count--;
    identifiersListA();

    getNextToken();
    if (currentToken->getText() != ":") syntaxError("Symbol ':' was not found!");

    type();

    getNextToken();
    if (currentToken->getText() != ";") syntaxError("Symbol ';' was not found!");

    varDeclarationListB();
}

void SyntaxCheck::identifiersListA() throw()
{
    getNextToken();
    if (currentToken->getClassification() != (Token::Classifications::IDENTIFIER)) syntaxError("Invalid identifier!");

    if(symbolsTable.getProgramName() == (currentToken->getText())) semanticError("Identifier has the same program name");

    //Checks whether the current identifier is declared elsewhere in the same scope
    if(symbolsTable.searchDuplicateDeclaration(currentToken->getText())) semanticError("Duplicate identifier!");

    //If not, put the identifier into stack
    string str_void = "void";
    symbolsTable.addSymbol(currentToken->getText(), str_void);
    identifiersListB();
}

void SyntaxCheck::identifiersListB() throw()
{
    getNextToken();
    if (currentToken->getText() != ",")
    {
        count--;
        return;
    }

    getNextToken();
    if (currentToken->getClassification() != (Token::Classifications::IDENTIFIER)) syntaxError("Invalid identifier!");

    if(symbolsTable.getProgramName() == (currentToken->getText())) semanticError("Identifier has the same program name");

    //Checks whether the current identifier is declared elsewhere in the same scope
    if(symbolsTable.searchDuplicateDeclaration(currentToken->getText())) semanticError("Duplicate identifier!");

    //If not, put the identifier into stack
    string str_void = "void";
    symbolsTable.addSymbol(currentToken->getText(), str_void);

    identifiersListB();
}

void SyntaxCheck::type() throw()
{
    getNextToken();
    if (!containsType(currentToken->getText())) syntaxError("Invalid type!");

    symbolsTable.assignType(currentToken->getText());
}

void SyntaxCheck::subProgramsDeclaration() throw()
{
    getNextToken();
    count--;
    if (currentToken->getText() == "procedure")
    {
        subProgram();
        subProgramsDeclaration();
    }
}

void SyntaxCheck::subProgram() throw()
{
    getNextToken();
    if (currentToken->getText() != "procedure")
    {
        count--;
        syntaxError("Keyword 'procedure' was not found!");
    }

    getNextToken();
    if (currentToken->getClassification() != (Token::Classifications::IDENTIFIER))
    {
        count--;
        syntaxError("Invalid identifier!");
    }

    if(symbolsTable.getProgramName() == (currentToken->getText())) semanticError("Identifier has the same program name");

    //Checks whether the current identifier is declared elsewhere in the same scope
    if(symbolsTable.searchDuplicateDeclaration(currentToken->getText())) semanticError("Duplicate identifier!");

    //If not, put the identifier into stack
    string pr = "procedure";
    symbolsTable.addSymbol(currentToken->getText(), pr);

    //enter into local scope
    symbolsTable.enterScope();

    arguments();

    getNextToken();
    if (currentToken->getText() != ";")
    {
        count--;
        syntaxError("Symbol ';' was not found!");
    }

    varDeclaration();
    subProgramsDeclaration();
    compoundCommand();
    //exit scope
    symbolsTable.exitScope();

    getNextToken();
    if (currentToken->getText() != ";")
    {
        count--;
        syntaxError("Symbol ';' was not found!");
    }
}

void SyntaxCheck::arguments() throw()
{
    getNextToken();
    if (currentToken->getText() != "(")
    {
        count--;
    }
    else
    {
        parameterListA();

        getNextToken();
        if (currentToken->getText() != ")") syntaxError("Symbol ')' was not found!");
    }
}

void SyntaxCheck::parameterListA() throw()
{
    identifiersListA();
    getNextToken();
    if (currentToken->getText() != ":") syntaxError("Symbol ':' was not found!");

    type();

    parameterListB();
}

void SyntaxCheck::parameterListB() throw()
{
    getNextToken();
    if (currentToken->getText() != ";")
    {
        count--;
        symbolsTable.assignParameters();
        return;
    }

    identifiersListA();

    getNextToken();
    if (currentToken->getText() != ":")
    {
        count--;
        syntaxError("Symbol ':' was not found!");
    }

    type();

    parameterListB();
}

void SyntaxCheck::compoundCommand() throw()
{
    getNextToken();
    if (currentToken->getText() != "begin") syntaxError("Keyword 'Begin' was not found!");

    optionalCommands();

    getNextToken();
    if (currentToken->getText() != "end") syntaxError("Keyword 'End' was not found!");
}

void SyntaxCheck::optionalCommands() throw()
{
    getNextToken();
    count--;
    if (currentToken->getText() != "end")
    {
        commandListA();
    }
}

void SyntaxCheck::commandListA() throw()
{
    command();
    commandListB();
}

void SyntaxCheck::commandListB() throw()
{
    getNextToken();
    if (currentToken->getText() == ";")
    {
        getNextToken();
        if (currentToken->getText() != "end")
        {
            count--;
            command();
            commandListB();
        }
        else
        {
            count--;
        }
    }
    else
    {
        count--;
    }
}

void SyntaxCheck::command() throw()
{
    getNextToken();
    if (currentToken->getClassification() == (Token::Classifications::IDENTIFIER))
    {
        if(symbolsTable.getProgramName() == (currentToken->getText())) semanticError("Program name cannot be used");

        //Checks whether the current identifier is declared elsewhere
        if(!symbolsTable.searchIdentifier(currentToken->getText())) semanticError("Using not declared identifier!");

        string &resultClassification = symbolsTable.getType(currentToken->getText());
        getNextToken();
        if (currentToken->getText() == ":=")
        {
            typeControl.pushMark();
            expression();

            typeControl.popMark();
            typeControl.verifyResult(resultClassification);
        }
        else
        {
            count -= 2;
            procedureActivationA();
            typeControl.reset();
        }
    }
    else if (currentToken->getText() == "if")
    {
        string resultClassification = "boolean";
        typeControl.pushMark();
        expression();


        typeControl.popMark();
        typeControl.verifyResult(resultClassification);


        getNextToken();
        if (currentToken->getText() != "then")
        {
            count--;
            syntaxError("Keyword 'Then' was not found!");
        }

        commandStructure();

        elsePart();
    }
    else if (currentToken->getText() == "while")
    {
        string resultClassification = "boolean";
        typeControl.pushMark();
        expression();

        typeControl.popMark();
        typeControl.verifyResult(resultClassification);

        getNextToken();
        if (currentToken->getText() != "do")
        {
            count--;
            syntaxError("Keyword 'do' was not found!");
        }

        commandStructure();
    }
    else if (currentToken->getText() == "do")
    {
        commandStructure();

        getNextToken();
        if (currentToken->getText() != "while")
        {
            count--;
            syntaxError("Keyword 'while' was not found!");
        }

        string resultClassification = "boolean";
        typeControl.pushMark();
        expression();

        typeControl.popMark();
        typeControl.verifyResult(resultClassification);

    }
    else if (currentToken->getText() == "begin")
    {
        //compoundCommand also reads 'begin'
        count--;
        compoundCommand();
    }
    else syntaxError("Invalid command.");

}
void SyntaxCheck::commandStructure() throw()
{
    getNextToken();
    count--;
    if (currentToken->getText() == "begin")
    {
        compoundCommand();
    }
    else
    {
        command();
    }
}

void SyntaxCheck::elsePart() throw()
{
    getNextToken();
    if (currentToken->getText() == "else")
    {
        commandStructure();
    }
    else
    {
        count--;
    }
}

void SyntaxCheck::procedureActivationA() throw()
{
    getNextToken();
    if (currentToken->getClassification() != Token::Classifications::IDENTIFIER) syntaxError("Invalid identifier!");
    typeControl.setCallProcedure(true, symbolsTable.getProcedure(currentToken->getText()));
    getNextToken();
    if (currentToken->getText() != "(")
    {
        count--;
        return;
    }

    count--;
    expressionListA();
}

void SyntaxCheck::expressionListA() throw()
{
    getNextToken();
    if (currentToken->getText() != "(")
    {
        count--;
    }
    else
    {

        typeControl.pushMark();
        expression();

        typeControl.popMark();

        if(typeControl.isCallProcedure()) {
            typeControl.pushParameter(typeControl.getFirstType());
            typeControl.reset();
        }

        expressionListB();

        getNextToken();
        if (currentToken->getText() != ")") syntaxError("Symbol ')' was not found!");
    }
}

void SyntaxCheck::expressionListB() throw()
{
    getNextToken();
    if (currentToken->getText()!= ",")
    {
        count--;
        typeControl.verifyResultProcedureCall();

        return;
    }

    typeControl.pushMark();
    expression();


    typeControl.popMark();

    if(typeControl.isCallProcedure()) {
        typeControl.pushParameter(typeControl.getFirstType());
        typeControl.reset();
    }

    expressionListB();
}

void SyntaxCheck::expression() throw()
{
    simpleExpressionA();

    getNextToken();
    if (currentToken->getClassification() != (Token::Classifications::RELATIONAL))
    {
        count--;
    }
    else
    {
        string rel = "relational";
        typeControl.pushOperation(rel);
        simpleExpressionA();
    }
}


void SyntaxCheck::simpleExpressionA() throw()
{
    getNextToken();
    if (currentToken->getText() != "+" && currentToken->getText() != "-")
    {
        count--;
    }

    termA();
    simpleExpressionB();
}

void SyntaxCheck::simpleExpressionB() throw()
{
    getNextToken();
    //stop simpleExpressionB's loop
    if (currentToken->getClassification() != (Token::Classifications::ADDITION))
    {
        count--;
    }
    else
    {
        string add = "addition";
        typeControl.pushOperation(add);
        termA();
        simpleExpressionB();
    }
}

void SyntaxCheck::termA() throw()
{
    factor();
    termB();
}

void SyntaxCheck::termB() throw()
{
    getNextToken();
    if (currentToken->getClassification() == (Token::Classifications::MULTIPLICATION))
    {
        string mult = "multiplication";
        typeControl.pushOperation(mult);
        factor();
        termB();
    }
    else
    {
        count--;
    }
}
void SyntaxCheck::factor() throw()
{
    getNextToken();
    if (currentToken->getClassification() == (Token::Classifications::IDENTIFIER))
    {
        if(symbolsTable.getProgramName() == (currentToken->getText())) semanticError("Program name cannot be used");

        //Checks whether the current identifier is declared elsewhere
        if(!symbolsTable.searchIdentifier(currentToken->getText())) semanticError("Using not declared identifier!");


        string &type = symbolsTable.getType(currentToken->getText());
        typeControl.pushType(type);

        getNextToken();
        count--;
        if (currentToken->getText() == "(")
        {
            expressionListA();
        }
    }
    else if (currentToken->getText() == "(")
    {
        typeControl.pushMark();
        expression();

        typeControl.popMark();


        getNextToken();
        if (currentToken->getText() != ")")
        {
            count--;
            syntaxError("Symbol ')' was not found!");
        }
    }
    else if (currentToken->getText() == "not")
    {
        factor();
    }
    else if (containsType(currentToken->getClassificationName()))
    {

        string type = currentToken->getClassificationName();
        typeControl.pushType(type);

    }
    else {
        count--;
        syntaxError("Expected factor.");
    }
}

